package org.xxpay.mch.user.ctrl;

import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xxpay.core.common.annotation.MethodLog;
import org.xxpay.core.common.constant.Constant;
import org.xxpay.core.common.constant.MchConstant;
import org.xxpay.core.common.constant.RetEnum;
import org.xxpay.core.common.domain.BizResponse;
import org.xxpay.core.common.domain.I18nBizResponse;
import org.xxpay.core.common.domain.XxPayResponse;
import org.xxpay.core.common.exception.ServiceException;
import org.xxpay.core.common.util.*;
import org.xxpay.core.common.vo.DBApplicationConfig;
import org.xxpay.core.common.vo.DBSmsConfig;
import org.xxpay.core.entity.MchInfo;
import org.xxpay.core.entity.SysLoginFailIpRecord;
import org.xxpay.mch.common.ctrl.BaseController;
import org.xxpay.mch.secruity.JwtAuthenticationRequest;
import org.xxpay.mch.secruity.JwtTokenUtil;
import org.xxpay.mch.user.service.UserService;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@RequestMapping(Constant.MCH_CONTROLLER_ROOT_PATH)
@RestController
public class AuthController extends BaseController {

    @Autowired
    private UserService userService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final String SMS_VERIFY_CODE = "SMS_VERIFY_CODE";

    private static Map<String, Integer> mobileSendMap = new HashMap<>();

    private static final MyLog _log = MyLog.getLog(AuthController.class);

    /**
     * 登录鉴权
     * @return
     * @throws AuthenticationException
     */
    @RequestMapping(value = "/auth")
    @MethodLog( remark = "登录" )
    public XxPayResponse authToken(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException, IOException {

        String vercode = getDecryptValStringRequired("vercode"); //图形验证码
        String vercodeRandomStr = getDecryptValStringRequired("vercodeRandomStr"); //图形验证码 缓存key

        String vercodeCacheValue = stringRedisTemplate.opsForValue().get(MchConstant.CACHEKEY_VERCODE_PREFIX_MCH + vercodeRandomStr);

        if(!vercode.equals(vercodeCacheValue)){
            return XxPayResponse.build(RetEnum.RET_COMM_VERCODE_ERROR);
        }

        String username = getDecryptValStringRequired("username");
        String password = getDecryptValStringRequired("password");
        Long googleCode = getDecryptValLong("googleCode"); //谷歌验证码

        JwtAuthenticationRequest authenticationRequest = new JwtAuthenticationRequest(username, password);
        String token;
        String ip = IPUtility.getClientIp(request);
        // 获取宝塔配置参数
        JSONObject sysConfig = commonService.sysConfigService.getSysConfig();
        try {
            // 查询当前IP是否被系统拉黑
            SysLoginFailIpRecord ipRecord = commonService.sysLoginFileIpRecordService.findById(ip);
            // 若存在当前拉黑IP并且拉黑类型为 0
            if (ipRecord != null && ipRecord.getLoginToBlackType() == MchConstant.LOGIN_TO_BLACK_SYS) {
                // 判断当前登录失败次数
                if (ipRecord.getFailCount() >= sysConfig.getInteger("loginCountConfig")) {
                    // 禁止登陆
                    return XxPayResponse.build(RetEnum.RET_MGR_USER_STOP);
                }
            }
           token = userService.login(authenticationRequest.getUsername(), authenticationRequest.getPassword(), ip, googleCode, sysConfig);
        }catch (ServiceException e) {
            return XxPayResponse.build(e);
        }

        MchInfo mchInfo = userService.findByLoginName(username);

        // 判断IP是否允许登录
        String clintIp = IPUtility.getClientIp(request);
        boolean isAllow = XXPayUtil.ipAllow(clintIp, mchInfo.getLoginWhiteIp(), mchInfo.getLoginBlackIp());
        if(!isAllow) {
            return XxPayResponse.build(RetEnum.RET_MCH_IP_NOT_LOGIN);
        }

        JSONObject data = new JSONObject();
        data.put("access_token", token);
        data.put("mchId", mchInfo.getMchId());
        data.put("loginSecurityType", mchInfo.getLoginSecurityType());
        stringRedisTemplate.delete(MchConstant.CACHEKEY_VERCODE_PREFIX_MCH + vercodeRandomStr);

        stringRedisTemplate.opsForValue().set(MchConstant.CACHEKEY_TOKEN_PREFIX_MCH + token, "1",
                MchConstant.CACHE_TOKEN_TIMEOUT, TimeUnit.SECONDS);

        // 登录成功删除本地IP限制信息
        commonService.sysLoginFileIpRecordService.delete(ip);

        return XxPayResponse.buildSuccessByAes(data.toJSONString());
    }


    /**
     * 登录鉴权(运营平台登录商户系统鉴权)
     * @return
     * @throws AuthenticationException
     */
    @RequestMapping(value = "/mgr_auth")
    public ResponseEntity<?> mgrAuthToken(HttpServletRequest request,
                                       HttpServletResponse response) throws AuthenticationException{
        JSONObject param = getJsonParam(request);
        Long mchId = getLongRequired(param, "mchId");
        String token = getStringRequired(param, "token");

        MchInfo mchInfo = userService.findByMchId(mchId);
        if(mchInfo == null) {
            return ResponseEntity.ok(I18nBizResponse.error("merchant.not.found"));
        }
        if(MchConstant.PUB_YES != mchInfo.getStatus()) {
            return ResponseEntity.ok(I18nBizResponse.error("merchant.status.disabled"));
        }

        // 先校验运营平台传过来的token,是否合法
        // 将商户ID+商户密码+密钥 做32位MD5加密转大写
        String password = mchInfo.getPassword();
        String secret = "Abc%$G&!!!128G";

        String signNowTime = DateUtil.date2Str(new Date(), "ddHHmm"); //当前时间
        String rawTokenByNow = mchId + password + secret + signNowTime;
        String md5Now = MD5Util.string2MD5(rawTokenByNow).toUpperCase();

        String sign1Time = DateUtil.date2Str(DateUtil.minusDateByMinute(new Date(), 1 ), "ddHHmm"); //当前时间减去1min
        String rawTokenBySub1 = mchId + password + secret + sign1Time;
        String md5BySub1 = MD5Util.string2MD5(rawTokenBySub1).toUpperCase();

        //验证当前时间 和 当前时间减1分钟是否满足（避免59s 跳转后失效的问题）
        if(!md5Now.equals(token) && !md5BySub1.equals(token)) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_ILLEGAL_LOGIN));
        }

        /*String rawToken = mchId + password + secret;
        String myToken = MD5Util.string2MD5(rawToken).toUpperCase();
        if(!myToken.equals(token)) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_ILLEGAL_LOGIN));
        }*/

        // 生成jwtToken返回
        String ip = IPUtility.getClientIp(request);
        String jwtToken = jwtTokenUtil.generateToken(mchId, String.valueOf(mchId), ip);

        stringRedisTemplate.opsForValue().set(MchConstant.CACHEKEY_TOKEN_PREFIX_MCH + jwtToken, "1",
                MchConstant.CACHE_TOKEN_TIMEOUT, TimeUnit.SECONDS);

        JSONObject data = new JSONObject();
        data.put("access_token", jwtToken);
        return ResponseEntity.ok(XxPayResponse.buildSuccess(data));
    }

    /**
     * 申请注册
     * @return
     * @throws AuthenticationException
     */
    @MethodLog( remark = "申请注册" )
    @RequestMapping(value = "/auth/register")
    public ResponseEntity<?> register(HttpServletRequest request) throws AuthenticationException{
        MchInfo mchInfo = getDecryptObject(MchInfo.class);
        if(mchInfo == null) mchInfo = new MchInfo();
        // 验证参数
        if (ObjectValidUtil.isInvalid(mchInfo.getName(), mchInfo.getEmail(), mchInfo.getMobile(), mchInfo.getPassword(), mchInfo.getType())) {
           return ResponseEntity.ok(BizResponse.build(RetEnum.RET_COMM_PARAM_NOT_FOUND));
        }
        // 判断密码
        if(!StrUtil.checkPassword(mchInfo.getPassword())) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_PASSWORD_FORMAT_FAIL));
        }

        String smsCode = getDecryptValStringRequired("vercode");
        Object obj = request.getSession().getAttribute(SMS_VERIFY_CODE);
        if(obj == null) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_SMS_VERIFY_FAIL));
        }

        JSONObject codeObj = (JSONObject) obj;
        String mobile = codeObj.getString("mobile");
        String code = codeObj.getString("code");
        Long time = codeObj.getLong("time");
        // 判断与发送验证码时手机是否一致
        if(!mchInfo.getMobile().toString().equals(mobile)) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_SMS_VERIFY_FAIL));
        }
        // 超过10分钟
        if(System.currentTimeMillis() > time + 1000 * 60 * 10) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_SMS_VERIFY_OVER_TIME));
        }
        // 判断验证码
        if(!smsCode.equals(code)) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_SMS_VERIFY_FAIL));
        }

        long resultByMchId;
        try {
            resultByMchId = userService.register(mchInfo);
        }catch (ServiceException e) {
            return ResponseEntity.ok(BizResponse.build(e.getRetEnum()));
        }
        if(resultByMchId <= 0) return ResponseEntity.ok(XxPayResponse.build(RetEnum.RET_MCH_REGISTER_FAIL));
        return ResponseEntity.ok(BizResponse.buildSuccess());
    }

    /**
     * 发送验证短信
     * @return
     * @throws AuthenticationException
     */
    @RequestMapping(value = "/auth/sms_send")
    public ResponseEntity<?> sendSms(HttpServletRequest request) throws AuthenticationException{
        JSONObject param = getJsonParam(request);
        // 验证参数
        String mobile = param.getString("phone");
        if(!StrUtil.checkMobileNumber(mobile)) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_MOBILE_FORMAT_ERROR));
        }
        // 验证手机号是否发送超限,一天5次
        // 暂时用内存(重启或分布式部署会防不住),真正使用时应改为redis
        String key = mobile + DateUtil.date2Str(new Date(), DateUtil.FORMAT_YYYY_MM_DD2);
        Integer times = mobileSendMap.get(mobile);
        if(times == null ) {
            mobileSendMap.put(key, 1);
        }else if(times <= 5) {
            mobileSendMap.put(key, times+1);
        }else {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_MOBILE_SEND_TOO_MUCH));
        }

        // 验证手机号是否被使用
        MchInfo mchInfo = userService.findByMobile(Long.parseLong(mobile));
        if(mchInfo != null) {
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_MOBILE_USED));
        }

        // 生成短信验证码
        String verifyCode = String.valueOf(new Random().nextInt(899999) + 100000);
        // 将短信验证码存在session中,分布式部署考虑放到redis
        JSONObject codeObj = new JSONObject();
        codeObj.put("mobile", mobile);
        codeObj.put("code", verifyCode);
        codeObj.put("time", System.currentTimeMillis());
        request.getSession().setAttribute(SMS_VERIFY_CODE, codeObj);
        _log.info("注册手机号:{},短信验证码:{}", mobile, verifyCode);
        // 发送短信服务
        try {
            sendSms(mobile, DBSmsConfig.BIZ_TYPE_REGISTER, "{\"code\":\""+verifyCode+"\"}");
            _log.info("调用阿里短信服务完成, mobile={}", mobile);
        } catch (ClientException e) {
            _log.error(e, "");
            return ResponseEntity.ok(BizResponse.build(RetEnum.RET_MCH_MOBILE_SEND_ERROR));
        }
        return ResponseEntity.ok(BizResponse.buildSuccess());
    }

    /**
     * 获取图形验证码
     */
    @RequestMapping(value = "/auth/vercode")
    public void getAuthCode(HttpServletRequest request, HttpServletResponse response, String vercodeRandomStr) throws IOException {

        if(StringUtils.isEmpty(vercodeRandomStr))return ;
        Map<String, Object> randomMap = RandomValidateCodeUtil.getRandcode(120, 40, 6, 20);
        String vercodeValue = randomMap.get("randomString").toString(); //码值
        stringRedisTemplate.opsForValue().set(MchConstant.CACHEKEY_VERCODE_PREFIX_MCH + vercodeRandomStr,
                vercodeValue, MchConstant.CACHEKEY_VERCODE_TIMEOUT, TimeUnit.MINUTES);
        BufferedImage randomImage = (BufferedImage)randomMap.get("randomImage");
        ImageIO.write(randomImage, "JPEG", response.getOutputStream());
    }

    /**
     * 获取网站标题
     */
    @RequestMapping(value = "/auth/siteTitle")
    public ResponseEntity<?>  getSiteTitle() {
        JSONObject siteJSON = commonService.sysConfigService.getSiteConfigJSON();
        DBApplicationConfig config = commonService.sysConfigService.getDBApplicationConfig();
        siteJSON.put("mgrSiteUrl", config.getMgrSiteUrl());
        siteJSON.put("mchSiteUrl", config.getMchSiteUrl());
        siteJSON.put("agentSiteUrl", config.getAgentSiteUrl());
        return ResponseEntity.ok(XxPayResponse.buildSuccess(siteJSON));
    }

}
